/*
 * %W% %E%
 * 
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.corba.se.impl.orb ;

import com.sun.corba.se.impl.orbutil.GetPropertyAction ;

import java.security.PrivilegedAction ;
import java.security.AccessController ;

import java.applet.Applet ;

import java.util.Properties ;
import java.util.Vector ;
import java.util.Set ;
import java.util.HashSet ;
import java.util.Enumeration ;
import java.util.Iterator ;
import java.util.StringTokenizer ;

import java.net.URL ;

import java.security.AccessController ;

import java.io.File ;
import java.io.FileInputStream ;

import com.sun.corba.se.spi.orb.DataCollector ;
import com.sun.corba.se.spi.orb.PropertyParser ;

import com.sun.corba.se.impl.orbutil.ORBConstants ;
import com.sun.corba.se.impl.orbutil.ORBUtility;

public abstract class DataCollectorBase implements DataCollector {
    private PropertyParser parser ;
    private Set propertyNames ;
    private Set propertyPrefixes ;
    private Set URLPropertyNames ;
    protected String localHostName ;
    protected String configurationHostName ;
    private boolean setParserCalled ;
    private Properties originalProps ;
    private Properties resultProps ;

    public DataCollectorBase( Properties props, String localHostName,
	String configurationHostName )
    {
	// XXX This is fully initialized here.  So do we ever want to 
	// generalize this (or perhaps this is the wrong place for this?)
	URLPropertyNames = new HashSet() ;
	URLPropertyNames.add( ORBConstants.INITIAL_SERVICES_PROPERTY ) ;

	propertyNames = new HashSet() ;

	// Make sure that we are ready to handle -ORBInitRef.  This is special
	// due to the need to handle multiple -ORBInitRef args as prefix
	// parsing.
	propertyNames.add( ORBConstants.ORB_INIT_REF_PROPERTY ) ;

	propertyPrefixes = new HashSet() ;

	this.originalProps = props ;
	this.localHostName = localHostName ;
	this.configurationHostName = configurationHostName ;
	setParserCalled = false ;
	resultProps = new Properties() ;
    }

//////////////////////////////////////////////////////////
// Public interface defined in DataCollector
//////////////////////////////////////////////////////////

    public boolean initialHostIsLocal()
    {
	checkSetParserCalled() ;
	return localHostName.equals( resultProps.getProperty( 
	    ORBConstants.INITIAL_HOST_PROPERTY ) ) ;
    }

    public void setParser( PropertyParser parser )
    {
	Iterator iter = parser.iterator() ;
	while (iter.hasNext()) {
	    ParserAction pa = (ParserAction)(iter.next()) ;
	    if (pa.isPrefix())
		propertyPrefixes.add( pa.getPropertyName() ) ;
	    else
		propertyNames.add( pa.getPropertyName() ) ;
	}

	collect() ;
	setParserCalled = true ;
    }

    public Properties getProperties()
    {
	checkSetParserCalled() ;
	return resultProps ;
    }

//////////////////////////////////////////////////////////
// public interface from DataCollector that must be defined
// in subclasses
//////////////////////////////////////////////////////////

    public abstract boolean isApplet() ;

//////////////////////////////////////////////////////////
// Implementation methods needed in subclasses
//////////////////////////////////////////////////////////

    protected abstract void collect() ;

//////////////////////////////////////////////////////////
// methods for use by subclasses
//////////////////////////////////////////////////////////

    protected void checkPropertyDefaults()
    {
        String host =
            resultProps.getProperty( ORBConstants.INITIAL_HOST_PROPERTY ) ;

        if ((host == null) || (host.equals(""))) 
            setProperty( ORBConstants.INITIAL_HOST_PROPERTY, 
		configurationHostName );

        String serverHost =
            resultProps.getProperty( ORBConstants.SERVER_HOST_PROPERTY ) ;

        if (serverHost == null || 
	    serverHost.equals("") ||
	    serverHost.equals("0.0.0.0") ||
	    serverHost.equals("::") ||
	    serverHost.toLowerCase().equals("::ffff:0.0.0.0"))
	{
            setProperty(ORBConstants.SERVER_HOST_PROPERTY,
			localHostName);
	    setProperty(ORBConstants.LISTEN_ON_ALL_INTERFACES,
			ORBConstants.LISTEN_ON_ALL_INTERFACES);
	}
    }

    protected void findPropertiesFromArgs( String[] params )
    {
        if (params == null)
            return;

        // All command-line args are of the form "-ORBkey value".
        // The key is mapped to <prefix>.ORBkey.

        String name ;
        String value ;

        for ( int i=0; i<params.length; i++ ) {
            value = null ;
            name = null ;

            if ( params[i] != null && params[i].startsWith("-ORB") ) {
                String argName = params[i].substring( 1 ) ;
                name = findMatchingPropertyName( propertyNames, argName ) ;

                if (name != null)
                    if ( i+1 < params.length && params[i+1] != null ) {
                        value = params[++i];
                    }
            }

            if (value != null) {
		setProperty( name, value ) ;
            }
        }
    }

    protected void findPropertiesFromApplet( final Applet app )
    {
        // Cannot use propertyPrefixes here, since there is no
	// way to fetch properties by prefix from an Applet.
        if (app == null)
            return;

        PropertyCallback callback = new PropertyCallback() {
	    public String get(String name) {
		return app.getParameter(name);
	    }
	} ;

	findPropertiesByName( propertyNames.iterator(), callback ) ;
    
        // Special Case:
        //
        // Convert any applet parameter relative URLs to an
        // absolute URL based on the Document Root. This is so HTML 
	// URLs can be kept relative which is sometimes useful for 
	// managing the Document Root layout.
	PropertyCallback URLCallback = new PropertyCallback() {
	    public String get( String name ) {
		String value = resultProps.getProperty(name);
		if (value == null)
		    return null ;

		try {
		    URL url = new URL( app.getDocumentBase(), value ) ;
		    return url.toExternalForm() ;
		} catch (java.net.MalformedURLException exc) {
		    // Just preserve the original (malformed) value:
		    // the error will be handled later.
		    return value ;
		}
	    }
	} ;

	findPropertiesByName( URLPropertyNames.iterator(), 
	    URLCallback ) ;
    }

    private void doProperties( final Properties props ) 
    {
        PropertyCallback callback =  new PropertyCallback() {
	    public String get(String name) {
		return props.getProperty(name);
	    }
	} ;
	
	findPropertiesByName( propertyNames.iterator(), callback ) ;

        findPropertiesByPrefix( propertyPrefixes, 
	    makeIterator( props.propertyNames()), callback );
    }

    protected void findPropertiesFromFile()
    {
        final Properties fileProps = getFileProperties() ;
        if (fileProps==null)
            return ;

	doProperties( fileProps ) ;
    }

    protected void findPropertiesFromProperties()
    {
        if (originalProps == null)
            return;

	doProperties( originalProps ) ;
    }

    //
    // Map System properties to ORB properties.
    // Security bug fix 4278205:
    // Only allow reading of system properties with ORB prefixes.
    // Previously a malicious subclass was able to read ANY system property.
    // Note that other prefixes are fine in other contexts; it is only
    // system properties that should impose a restriction.
    protected void findPropertiesFromSystem()
    {
	Set normalNames = getCORBAPrefixes( propertyNames ) ;
	Set prefixNames = getCORBAPrefixes( propertyPrefixes ) ;

	PropertyCallback callback = new PropertyCallback() {
	    public String get(String name) {
		return getSystemProperty(name);
	    }
	} ;

	findPropertiesByName( normalNames.iterator(), callback ) ;

        findPropertiesByPrefix( prefixNames,
	    getSystemPropertyNames(), callback ) ;
    }

//////////////////////////////////////////////////////////
// internal implementation
//////////////////////////////////////////////////////////

    // Store name, value in resultProps, with special 
    // treatment of ORBInitRef.  All updates to resultProps
    // must happen through this method.
    private void setProperty( String name, String value )
    {
	if( name.equals( ORBConstants.ORB_INIT_REF_PROPERTY ) ) {
	    // Value is <name>=<URL>
	    StringTokenizer st = new StringTokenizer( value, "=" ) ;
	    if (st.countTokens() != 2)
		throw new IllegalArgumentException() ;

	    String refName = st.nextToken() ;
	    String refValue = st.nextToken() ;

	    resultProps.setProperty( name + "." + refName, refValue ) ;
	} else {
	    resultProps.setProperty( name, value ) ;
	}
    }

    private void checkSetParserCalled()
    {
	if (!setParserCalled)
	    throw new IllegalStateException( "setParser not called." ) ;
    }

    // For each prefix in prefixes, For each name in propertyNames,
    // if (prefix is a prefix of name) get value from getProperties and
    // setProperty (name, value).
    private void findPropertiesByPrefix( Set prefixes, 
	Iterator propertyNames, PropertyCallback getProperty )
    {
	while (propertyNames.hasNext()) {
	    String name = (String)(propertyNames.next()) ;
	    Iterator iter = prefixes.iterator() ;
	    while (iter.hasNext()) {
		String prefix = (String)(iter.next()) ;
		if (name.startsWith( prefix )) {
		    String value = getProperty.get( name ) ;

                    // Note: do a put even if value is null since just
                    // the presence of the property may be significant.
		    setProperty( name, value ) ;
		}
	    }
	}
    }

    // For each prefix in names, get the corresponding property
    // value from the callback, and store the name/value pair in
    // the result.
    private void findPropertiesByName( Iterator names, 
	PropertyCallback getProperty )
    {
	while (names.hasNext()) {
	    String name = (String)(names.next()) ;
	    String value = getProperty.get( name ) ;
	    if (value != null)
		setProperty( name, value ) ;
	}
    }

    private static String getSystemProperty(final String name)
    {
        return (String)AccessController.doPrivileged(
	    new GetPropertyAction(name));
    }

    // Map command-line arguments to ORB properties.
    //
    private String findMatchingPropertyName( Set names,
	String suffix ) 
    {
	Iterator iter = names.iterator() ;
	while (iter.hasNext()) {
	    String name = (String)(iter.next()) ;
	    if (name.endsWith( suffix ))
		return name ;
	}

	return null ;
    }

    private static Iterator makeIterator( final Enumeration enumeration )
    {
	return new Iterator() {
	    public boolean hasNext() { return enumeration.hasMoreElements() ; }
	    public Object next() { return enumeration.nextElement() ; }
	    public void remove() { throw new UnsupportedOperationException() ; }
	} ;
    }

    private static Iterator getSystemPropertyNames()
    {
        // This will not throw a SecurityException because this
        // class was loaded from rt.jar using the bootstrap classloader.
        Enumeration enumeration = (Enumeration)
            AccessController.doPrivileged(
                new PrivilegedAction() {
                      public java.lang.Object run() {
                          return System.getProperties().propertyNames();
                      }
                }
	    );

	return makeIterator( enumeration ) ;
    }

    private void getPropertiesFromFile( Properties props, String fileName )
    {
        try {
	    File file = new File( fileName ) ;
	    if (!file.exists())
		return ;

            FileInputStream in = new FileInputStream( file ) ;
	    
	    try {
		props.load( in ) ;
	    } finally {
		in.close() ;
	    }
        } catch (Exception exc) {
            // if (ORBInitDebug)
                // dprint( "ORB properties file " + fileName + " not found: " + 
		    // exc) ;
        }
    }

    private Properties getFileProperties()
    {
        Properties defaults = new Properties() ;

	String javaHome = getSystemProperty( "java.home" ) ;
	String fileName = javaHome + File.separator + "lib" + File.separator +
	    "orb.properties" ;

	getPropertiesFromFile( defaults, fileName ) ;

	Properties results = new Properties( defaults ) ;

        String userHome = getSystemProperty( "user.home" ) ;
        fileName = userHome + File.separator + "orb.properties" ;

	getPropertiesFromFile( results, fileName ) ;
	return results ;
    }

    private boolean hasCORBAPrefix( String prefix )
    {
	return prefix.startsWith( ORBConstants.ORG_OMG_PREFIX ) ||
	    prefix.startsWith( ORBConstants.SUN_PREFIX ) ||
	    prefix.startsWith( ORBConstants.SUN_LC_PREFIX ) ||
	    prefix.startsWith( ORBConstants.SUN_LC_VERSION_PREFIX ) ;
    }

    // Return only those element of prefixes for which hasCORBAPrefix 
    // is true.  
    private Set getCORBAPrefixes( final Set prefixes )
    {
	Set result = new HashSet() ;
	Iterator iter = prefixes.iterator() ;
	while (iter.hasNext()) {
	    String element = (String)(iter.next()) ;
	    if (hasCORBAPrefix( element )) 
		result.add( element ) ;
	}

	return result ;
    }
}

// Used to collect properties from various sources.
abstract class PropertyCallback
{
    abstract public String get(String name);
}
